/*
 * Copyright 2012 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.workbench.screens.guided.rule.client.widget;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import org.drools.workbench.models.datamodel.oracle.DataType;
import org.drools.workbench.models.datamodel.oracle.ModelField;
import org.drools.workbench.models.datamodel.oracle.OperatorsOracle;
import org.drools.workbench.models.datamodel.rule.CompositeFieldConstraint;
import org.drools.workbench.models.datamodel.rule.FactPattern;
import org.drools.workbench.models.datamodel.rule.FieldConstraint;
import org.drools.workbench.models.datamodel.rule.HasCEPWindow;
import org.drools.workbench.models.datamodel.rule.HasConstraints;
import org.drools.workbench.models.datamodel.rule.IPattern;
import org.drools.workbench.models.datamodel.rule.RuleAttribute;
import org.drools.workbench.models.datamodel.rule.RuleModel;
import org.drools.workbench.models.datamodel.rule.SingleFieldConstraint;
import org.drools.workbench.models.datamodel.rule.SingleFieldConstraintEBLeftSide;
import org.drools.workbench.models.datamodel.rule.builder.DRLConstraintValueBuilder;
import org.drools.workbench.models.datamodel.rule.visitors.ToStringExpressionVisitor;
import org.drools.workbench.screens.guided.rule.client.editor.CEPOperatorsDropdown;
import org.drools.workbench.screens.guided.rule.client.editor.CEPWindowOperatorsDropdown;
import org.drools.workbench.screens.guided.rule.client.editor.ConstraintValueEditor;
import org.drools.workbench.screens.guided.rule.client.editor.ExpressionTypeChangeEvent;
import org.drools.workbench.screens.guided.rule.client.editor.ExpressionTypeChangeHandler;
import org.drools.workbench.screens.guided.rule.client.editor.OperatorSelection;
import org.drools.workbench.screens.guided.rule.client.editor.RuleModeller;
import org.drools.workbench.screens.guided.rule.client.editor.factPattern.Connectives;
import org.drools.workbench.screens.guided.rule.client.editor.factPattern.PopupCreator;
import org.drools.workbench.screens.guided.rule.client.resources.GuidedRuleEditorResources;
import org.drools.workbench.screens.guided.rule.client.resources.images.GuidedRuleEditorImages508;
import org.gwtbootstrap3.client.ui.TextBox;
import org.kie.workbench.common.widgets.client.resources.HumanReadable;
import org.kie.workbench.common.widgets.client.resources.i18n.HumanReadableConstants;
import org.uberfire.client.callbacks.Callback;
import org.uberfire.ext.widgets.common.client.common.ClickableLabel;
import org.uberfire.ext.widgets.common.client.common.SmallLabel;

/**
 * This is the new smart widget that works off the model.
 */
public class FactPatternWidget extends RuleModellerWidget {

    private FactPattern pattern;
    private FlexTable layout = new FlexTable();
    private Connectives connectives;
    private PopupCreator popupCreator;
    private boolean bindable;
    private boolean isAll0WithLabel;
    private boolean readOnly;

    private boolean isFactTypeKnown;

    private final Map<SingleFieldConstraint, ConstraintValueEditor> constraintValueEditors = new HashMap<SingleFieldConstraint, ConstraintValueEditor>();
    private ConstraintValueEditor constraintValueEditor;

    public FactPatternWidget( RuleModeller mod,
                              EventBus eventBus,
                              IPattern p,
                              boolean canBind ) {
        this( mod,
              eventBus,
              p,
              false,
              canBind,
              null );
    }

    /**
     * Creates a new FactPatternWidget
     * @param canBind
     * @param readOnly if the widget should be in RO mode. If this parameter is null,
     * the readOnly attribute is calculated.
     */
    public FactPatternWidget( RuleModeller ruleModeller,
                              EventBus eventBus,
                              IPattern pattern,
                              boolean canBind,
                              Boolean readOnly ) {
        this( ruleModeller,
              eventBus,
              pattern,
              false,
              canBind,
              readOnly );
    }

    public FactPatternWidget( RuleModeller mod,
                              EventBus eventBus,
                              IPattern p,
                              boolean isAll0WithLabel,
                              boolean canBind,
                              Boolean readOnly ) {
        super( mod,
               eventBus );
        this.pattern = (FactPattern) p;
        this.bindable = canBind;

        this.popupCreator = new PopupCreator();
        this.popupCreator.setBindable( bindable );
        this.popupCreator.setDataModelOracle( mod.getDataModelOracle() );
        this.popupCreator.setModeller( mod );
        this.popupCreator.setPattern( pattern );

        this.isAll0WithLabel = isAll0WithLabel;

        //if readOnly == null, the RO attribute is calculated.
        this.isFactTypeKnown = mod.getDataModelOracle().isFactTypeRecognized( this.pattern.getFactType() );
        if ( readOnly == null ) {
            this.readOnly = !this.isFactTypeKnown;
        } else {
            this.readOnly = readOnly;
        }

        this.connectives = new Connectives( mod,
                                            eventBus,
                                            pattern,
                                            this.readOnly );

        layout.setWidget( 0,
                          0,
                          getPatternLabel( this.pattern ) );
        FlexCellFormatter formatter = layout.getFlexCellFormatter();
        formatter.setAlignment( 0,
                                0,
                                HasHorizontalAlignment.ALIGN_LEFT,
                                HasVerticalAlignment.ALIGN_BOTTOM );
        formatter.setStyleName( 0,
                                0,
                                "modeller-fact-TypeHeader" );

        List<FieldConstraint> sortedConst = sortConstraints( pattern.getFieldConstraints() );
        pattern.setFieldConstraints( sortedConst );
        drawConstraints( sortedConst,
                         pattern );

        //CEP 'window' widget
        int row = layout.getRowCount() + 1;
        layout.setWidget( row,
                          0,
                          createCEPWindowWidget( mod,
                                                 pattern ) );

        if ( this.readOnly ) {
            layout.addStyleName( "editor-disabled-widget" );
        }

        initWidget( layout );

    }

    /**
     * Render a hierarchy of constraints, hierarchy here means constraints that
     * may themselves depend on members of constraint objects. With this code,
     * the GUI enables clicking rules of the form: $result = RoutingResult(
     * NerOption.types contains "arzt" )
     * @param sortedConst a sorted list of constraints to display.
     */
    private void drawConstraints( List<FieldConstraint> sortedConst,
                                  HasConstraints hasConstraints ) {
        final FlexTable table = new FlexTable();
        layout.setWidget( 1,
                          0,
                          table );
        List<FieldConstraint> parents = new ArrayList<FieldConstraint>();

        for ( int i = 0; i < sortedConst.size(); i++ ) {
            traverseSingleFieldConstraints( sortedConst,
                                            table,
                                            parents,
                                            hasConstraints,
                                            i );

            //now the clear icon
            final int currentRow = i;
            Image clear = GuidedRuleEditorImages508.INSTANCE.DeleteItemSmall();
            clear.setTitle( GuidedRuleEditorResources.CONSTANTS.RemoveThisWholeRestriction() );
            clear.addClickHandler( createClickHandlerForClearImageButton( currentRow ) );

            if ( !this.readOnly ) {
                //This used to be 5 and Connectives were not rendered
                table.setWidget( currentRow,
                                 6,
                                 clear );
            }

        }
    }

    private ClickHandler createClickHandlerForClearImageButton( final int currentRow ) {
        return new ClickHandler() {

            public void onClick( ClickEvent event ) {
                if ( Window.confirm( GuidedRuleEditorResources.CONSTANTS.RemoveThisItem() ) ) {
                    setModified( true );
                    pattern.removeConstraint( currentRow );
                    getModeller().refreshWidget();
                }
            }
        };
    }

    private void traverseSingleFieldConstraints( List<FieldConstraint> sortedConst,
                                                 final FlexTable table,
                                                 List<FieldConstraint> parents,
                                                 HasConstraints hasConstraints,
                                                 int i ) {
        int tabs = -1;
        FieldConstraint current = sortedConst.get( i );
        if ( current instanceof SingleFieldConstraint ) {
            SingleFieldConstraint single = (SingleFieldConstraint) current;
            FieldConstraint parent = single.getParent();

            for ( int j = 0; j < parents.size(); j++ ) {
                FieldConstraint storedParent = parents.get( j );
                if ( storedParent != null && storedParent.equals( parent ) ) {
                    tabs = j + 1;
                    traverseForRemoval( parents,
                                        j );
                    parents.add( current );
                    break;
                }
            }

            if ( tabs < 0 ) {
                tabs = 0;
                parents.add( current );
            }
        }
        renderFieldConstraint( table,
                               i,
                               current,
                               hasConstraints,
                               true,
                               tabs );
    }

    private void traverseForRemoval( List<FieldConstraint> parents,
                                     int j ) {
        for ( int k = j + 1; k < parents.size(); k++ ) {
            parents.remove( j + 1 );
        }
    }

    /**
     * Sort the rule constraints such that parent rules are inserted directly
     * before their child rules.
     * @param constraints the list of inheriting constraints to sort.
     * @return a sorted list of constraints ready for display.
     */
    private List<FieldConstraint> sortConstraints( FieldConstraint[] constraints ) {
        List<FieldConstraint> sortedConst = new ArrayList<FieldConstraint>( constraints.length );
        for ( int i = 0; i < constraints.length; i++ ) {
            FieldConstraint current = constraints[ i ];
            if ( current instanceof SingleFieldConstraint ) {
                SingleFieldConstraint single = (SingleFieldConstraint) current;
                int index = sortedConst.indexOf( single.getParent() );
                if ( single.getParent() == null ) {
                    sortedConst.add( single );
                } else if ( index >= 0 ) {
                    sortedConst.add( index + 1,
                                     single );
                } else {
                    insertSingleFieldConstraint( single,
                                                 sortedConst );
                }
            } else {
                sortedConst.add( current );
            }
        }
        return sortedConst;
    }

    /**
     * Recursively add constraints and their parents.
     * @param sortedConst the array to fill.
     * @param fieldConst the constraint to investigate.
     */
    private void insertSingleFieldConstraint( SingleFieldConstraint fieldConst,
                                              List<FieldConstraint> sortedConst ) {
        if ( fieldConst.getParent() instanceof SingleFieldConstraint ) {
            insertSingleFieldConstraint( (SingleFieldConstraint) fieldConst.getParent(),
                                         sortedConst );
        }
        sortedConst.add( fieldConst );
    }

    /**
     * This will render a field constraint into the given table. The row is the
     * row number to stick it into.
     */
    private void renderFieldConstraint( final FlexTable inner,
                                        int row,
                                        FieldConstraint constraint,
                                        HasConstraints hasConstraints,
                                        boolean showBinding,
                                        int tabs ) {
        //if nesting, or predicate, then it will need to span 5 cols.
        if ( constraint instanceof SingleFieldConstraint ) {
            renderSingleFieldConstraint( inner,
                                         row,
                                         (SingleFieldConstraint) constraint,
                                         hasConstraints,
                                         showBinding,
                                         tabs );
        } else if ( constraint instanceof CompositeFieldConstraint ) {
            inner.setWidget( row,
                             1,
                             compositeFieldConstraintEditor( (CompositeFieldConstraint) constraint ) );
            inner.getFlexCellFormatter().setColSpan( row,
                                                     1,
                                                     5 );
            inner.setWidget( row,
                             0,
                             new HTML( "&nbsp;&nbsp;&nbsp;&nbsp;" ) ); //NON-NLS
        }
    }

    /**
     * This will show the constraint editor - allowing field constraints to be
     * nested etc.
     */
    private Widget compositeFieldConstraintEditor( final CompositeFieldConstraint constraint ) {
        FlexTable t = new FlexTable();
        String desc = null;

        ClickHandler click = new ClickHandler() {

            public void onClick( ClickEvent event ) {
                popupCreator.showPatternPopupForComposite( constraint );
            }
        };

        if ( constraint.getCompositeJunctionType().equals( CompositeFieldConstraint.COMPOSITE_TYPE_AND ) ) {
            desc = GuidedRuleEditorResources.CONSTANTS.AllOf() + ":";
        } else {
            desc = GuidedRuleEditorResources.CONSTANTS.AnyOf() + ":";
        }

        t.setWidget( 0,
                     0,
                     new ClickableLabel( desc,
                                         click,
                                         !this.readOnly ) );
        t.getFlexCellFormatter().setColSpan( 0,
                                             0,
                                             2 );

        FieldConstraint[] nested = constraint.getConstraints();
        FlexTable inner = new FlexTable();
        if ( nested != null ) {
            for ( int i = 0; i < nested.length; i++ ) {
                this.renderFieldConstraint( inner,
                                            i,
                                            nested[ i ],
                                            constraint,
                                            true,
                                            0 );
                //add in remove icon here...
                final int currentRow = i;
                Image clear = GuidedRuleEditorImages508.INSTANCE.DeleteItemSmall();
                clear.setTitle( GuidedRuleEditorResources.CONSTANTS.RemoveThisNestedRestriction() );
                clear.addClickHandler( new ClickHandler() {

                    public void onClick( ClickEvent event ) {
                        if ( Window.confirm( GuidedRuleEditorResources.CONSTANTS.RemoveThisItemFromNestedConstraint() ) ) {
                            setModified( true );
                            constraint.removeConstraint( currentRow );
                            getModeller().refreshWidget();
                        }
                    }
                } );
                if ( !this.readOnly ) {
                    //This used to be 5 and Connectives were not rendered
                    inner.setWidget( i,
                                     6,
                                     clear );
                }
            }
        }

        t.setWidget( 1,
                     1,
                     inner );
        t.setWidget( 1,
                     0,
                     new HTML( "&nbsp;&nbsp;&nbsp;&nbsp;" ) );
        return t;
    }

    /**
     * Applies a single field constraint to the given table, and start row.
     */
    private void renderSingleFieldConstraint( final FlexTable inner,
                                              final int row,
                                              final SingleFieldConstraint constraint,
                                              final HasConstraints hasConstraints,
                                              boolean showBinding,
                                              final int tabs ) {

        final int col = 1; //for offsetting, just a slight indent

        inner.setWidget( row,
                         0,
                         new HTML( "&nbsp;&nbsp;&nbsp;&nbsp;" ) );
        if ( constraint.getConstraintValueType() != SingleFieldConstraint.TYPE_PREDICATE ) {

            HorizontalPanel ebContainer = null;
            if ( constraint instanceof SingleFieldConstraintEBLeftSide ) {
                ebContainer = expressionBuilderLS( (SingleFieldConstraintEBLeftSide) constraint,
                                                   showBinding );
                inner.setWidget( row,
                                 0 + col,
                                 ebContainer );
            } else {
                inner.setWidget( row,
                                 0 + col,
                                 fieldLabel( constraint,
                                             hasConstraints,
                                             showBinding,
                                             tabs * 20 ) );
            }
            inner.setWidget( row,
                             1 + col,
                             operatorDropDown( constraint,
                                               inner,
                                               row,
                                               2 + col ) );
            //Get first part of constraint.fieldName? #1=Fact1, #2=SubFact1
            inner.setWidget( row,
                             2 + col,
                             createValueEditor( constraint ) );
            inner.setWidget( row,
                             3 + col,
                             connectives.connectives( constraint ) );

            if ( ebContainer != null && ebContainer.getWidgetCount() > 0 ) {
                if ( ebContainer.getWidget( 0 ) instanceof ExpressionBuilder ) {
                    associateExpressionWithChangeHandler( inner,
                                                          row,
                                                          constraint,
                                                          col,
                                                          ebContainer );
                }
            }

        } else if ( constraint.getConstraintValueType() == SingleFieldConstraint.TYPE_PREDICATE ) {
            inner.setWidget( row,
                             1,
                             predicateEditor( constraint ) );
            inner.getFlexCellFormatter().setColSpan( row,
                                                     1,
                                                     5 );
        }
    }

    //Widget for CEP 'windows'
    private Widget createCEPWindowWidget( final RuleModeller modeller,
                                          final HasCEPWindow c ) {
        final HorizontalPanel hp = new HorizontalPanel();
        modeller.getDataModelOracle().isFactTypeAnEvent( pattern.getFactType(),
                                                         new Callback<Boolean>() {
                                                             @Override
                                                             public void callback( final Boolean result ) {
                                                                 if ( Boolean.TRUE.equals( result ) ) {
                                                                     final Label lbl = new Label( HumanReadableConstants.INSTANCE.OverCEPWindow() );
                                                                     lbl.setStyleName( "paddedLabel" );
                                                                     hp.add( lbl );

                                                                     final CEPWindowOperatorsDropdown cwo = new CEPWindowOperatorsDropdown( c,
                                                                                                                                            readOnly );

                                                                     if ( !isReadOnly() ) {
                                                                         cwo.addValueChangeHandler( new ValueChangeHandler<OperatorSelection>() {

                                                                             public void onValueChange( ValueChangeEvent<OperatorSelection> event ) {
                                                                                 setModified( true );
                                                                                 OperatorSelection selection = event.getValue();
                                                                                 String selected = selection.getValue();
                                                                                 c.getWindow().setOperator( selected );
                                                                             }
                                                                         } );
                                                                     }

                                                                     hp.add( cwo );
                                                                 }
                                                             }
                                                         } );
        return hp;
    }

    private void associateExpressionWithChangeHandler( final FlexTable inner,
                                                       final int row,
                                                       final SingleFieldConstraint constraint,
                                                       final int col,
                                                       HorizontalPanel ebContainer ) {
        ExpressionBuilder eb = (ExpressionBuilder) ebContainer.getWidget( 0 );
        eb.addExpressionTypeChangeHandler( new ExpressionTypeChangeHandler() {

            public void onExpressionTypeChanged( ExpressionTypeChangeEvent event ) {
                try {
                    //Change "operator" drop-down as the content depends on data-type
                    constraint.setFieldType( event.getNewType() );
                    inner.setWidget( row,
                                     1 + col,
                                     operatorDropDown( constraint,
                                                       inner,
                                                       row,
                                                       2 + col ) );
                    //Change "value" editor to the pen icon as the applicable Widget depends on data-type
                    constraint.setConstraintValueType( SingleFieldConstraint.TYPE_UNDEFINED );
                    constraint.setValue( "" );
                    inner.setWidget( row,
                                     2 + col,
                                     createValueEditor( constraint ) );
                } catch ( Exception e ) {
                    e.printStackTrace();
                }
            }
        } );
    }

    /**
     * This provides an inline formula editor, not unlike a spreadsheet does.
     */
    private Widget predicateEditor( final SingleFieldConstraint c ) {

        HorizontalPanel pred = new HorizontalPanel();
        pred.setWidth( "100%" );
        Image img = new Image( GuidedRuleEditorResources.INSTANCE.images().functionAssets() );
        img.setTitle( GuidedRuleEditorResources.CONSTANTS.FormulaBooleanTip() );

        pred.add( img );
        if ( c.getValue() == null ) {
            c.setValue( "" );
        }

        final TextBox box = new TextBox();
        box.setText( c.getValue() );

        if ( !this.readOnly ) {
            box.addChangeHandler( new ChangeHandler() {

                public void onChange( ChangeEvent event ) {
                    setModified( true );
                    c.setValue( box.getText() );
                }
            } );
            box.setWidth( "100%" );
            pred.add( box );
        } else {
            pred.add( new SmallLabel( c.getValue() ) );
        }

        return pred;
    }

    /**
     * This returns the pattern label.
     */
    private Widget getPatternLabel( final FactPattern fp ) {
        ClickHandler click = new ClickHandler() {

            public void onClick( ClickEvent event ) {
                popupCreator.showPatternPopup( fp,
                                               null,
                                               false );
            }
        };

        String patternName = ( pattern.isBound() ) ? pattern.getFactType() + " <b>[" + pattern.getBoundName() + "]</b>" : pattern.getFactType();

        String desc;
        if ( isAll0WithLabel ) {
            desc = GuidedRuleEditorResources.CONSTANTS.All0with( patternName );
        } else {
            if ( pattern.getNumberOfConstraints() > 0 ) {
                desc = GuidedRuleEditorResources.CONSTANTS.ThereIsAAn0With( patternName );
            } else {
                desc = GuidedRuleEditorResources.CONSTANTS.ThereIsAAn0( patternName );
            }
            desc = anA( desc,
                        patternName );
        }

        return new ClickableLabel( desc,
                                   click,
                                   !this.readOnly );
    }

    /**
     * Change to an/a depending on context - only for english TODO use GWT
     * support for that:
     * http://code.google.com/intl/nl/webtoolkit/doc/latest/DevGuideI18n.html
     */
    private String anA( String desc,
                        String patternName ) {
        if ( desc.startsWith( "There is a/an" ) ) { //NON-NLS
            String vowel = patternName.substring( 0,
                                                  1 );
            if ( vowel.equalsIgnoreCase( "A" ) || vowel.equalsIgnoreCase( "E" ) || vowel.equalsIgnoreCase( "I" ) || vowel.equalsIgnoreCase( "O" ) || vowel.equalsIgnoreCase( "U" ) ) { //NON-NLS
                return desc.replace( "There is a/an",
                                     "There is an" ); //NON-NLS
            } else {
                return desc.replace( "There is a/an",
                                     "There is a" ); //NON-NLS
            }
        } else {
            return desc;
        }
    }

    private Widget createValueEditor( final SingleFieldConstraint constraint ) {

        constraintValueEditor = new ConstraintValueEditor( constraint,
                                                           pattern.getConstraintList(),
                                                           this.getModeller(),
                                                           this.getEventBus(),
                                                           this.readOnly );
        //If any literal value changes set to dirty and refresh dependent enumerations
        constraintValueEditor.setOnValueChangeCommand( new Command() {
            public void execute() {
                constraintValueEditor.hideError();
                setModified( true );
                refreshConstraintValueEditorsDropDownData( constraint );
            }
        } );
        //If a Template Key value changes only set to dirty
        constraintValueEditor.setOnTemplateValueChangeCommand( new Command() {
            public void execute() {
                constraintValueEditor.hideError();
                setModified( true );
            }
        } );

        //Keep a reference to the value editors so they can be refreshed for dependent enums
        constraintValueEditors.put( constraint,
                                    constraintValueEditor );

        return constraintValueEditor;
    }

    private Widget operatorDropDown( final SingleFieldConstraint constraint,
                                     final FlexTable inner,
                                     final int row,
                                     final int col ) {
        final HorizontalPanel hp = new HorizontalPanel();
        if ( !this.readOnly ) {

            getOperatorDropDown( constraint, inner, row, col, new Callback<CEPOperatorsDropdown>() {
                @Override
                public void callback( CEPOperatorsDropdown result ) {
                    hp.add( result );
                }
            } );

        } else {
            final SmallLabel sl = new SmallLabel( "<b>" + ( constraint.getOperator() == null ? GuidedRuleEditorResources.CONSTANTS.pleaseChoose() : HumanReadable.getOperatorDisplayName( constraint.getOperator() ) ) + "</b>" );
            hp.add( sl );
        }
        return hp;
    }

    private void getOperatorDropDown( final SingleFieldConstraint constraint,
                                      final FlexTable inner,
                                      final int row,
                                      final int col,
                                      final Callback<CEPOperatorsDropdown> callback ) {
        String fieldName;
        String factType;

        //Connectives Operators are handled in class Connectives
        if ( constraint instanceof SingleFieldConstraintEBLeftSide ) {
            SingleFieldConstraintEBLeftSide sfexp = (SingleFieldConstraintEBLeftSide) constraint;
            factType = sfexp.getExpressionLeftSide().getPreviousClassType();
            if ( factType == null ) {
                factType = sfexp.getExpressionLeftSide().getClassType();
            }
            fieldName = sfexp.getExpressionLeftSide().getFieldName();

        } else {
            factType = constraint.getFactType();
            fieldName = constraint.getFieldName();
        }

        getOperatorCompletions( constraint, inner, row, col, callback, fieldName, factType );
    }

    private void getOperatorCompletions( final SingleFieldConstraint constraint,
                                         final FlexTable inner,
                                         final int row,
                                         final int col,
                                         final Callback<CEPOperatorsDropdown> callback,
                                         String fieldName,
                                         String factType ) {
        connectives.getDataModelOracle().getOperatorCompletions( factType,
                                                                 fieldName,
                                                                 new Callback<String[]>() {
                                                                     @Override
                                                                     public void callback( final String[] operators ) {
                                                                         CEPOperatorsDropdown dropdown = new CEPOperatorsDropdown( operators,
                                                                                                                                   constraint );
                                                                         callback.callback( dropdown );

                                                                         dropdown.addValueChangeHandler( new ValueChangeHandler<OperatorSelection>() {

                                                                             public void onValueChange( ValueChangeEvent<OperatorSelection> event ) {
                                                                                 onDropDownValueChanged( event, constraint, inner, row, col );
                                                                             }
                                                                         } );
                                                                     }
                                                                 } );
    }

    private void onDropDownValueChanged( ValueChangeEvent<OperatorSelection> event,
                                         SingleFieldConstraint constraint,
                                         FlexTable inner,
                                         int row,
                                         int col ) {
        setModified( true );
        final String selected = event.getValue().getValue();
        final String selectedText = event.getValue().getDisplayText();
        final String originalOperator = constraint.getOperator();

        //Prevent recursion once operator change has been applied
        if ( selectedText.equals( constraint.getOperator() ) ) {
            return;
        }

        constraint.setOperator( selected );
        if ( constraint.getOperator().equals( "" ) ) {
            constraint.setOperator( null );
            constraintValueEditor.hideError();
        } else {
            constraintValueEditor.showError();
        }

        if ( inner != null ) {
            if ( isWidgetForValueNeeded( selectedText ) ) {
                inner.getWidget( row, col ).setVisible( false );
            } else {
                inner.getWidget( row, col ).setVisible( true );
            }
        }

        //If new operator requires a comma separated list and old did not, or vice-versa
        //we need to redraw the ConstraintValueEditor for the constraint
        if ( OperatorsOracle.operatorRequiresList( selected ) != OperatorsOracle.operatorRequiresList( originalOperator ) ) {
            if ( OperatorsOracle.operatorRequiresList( selected ) == false ) {
                final String[] oldValueList = constraint.getValue().split( "," );
                if ( oldValueList.length > 0 ) {
                    constraint.setValue( oldValueList[ 0 ] );
                }
            }

            //Redraw ConstraintValueEditor
            inner.setWidget(
                    row,
                    col,
                    createValueEditor( constraint ) );
        }
    }

    private boolean isWidgetForValueNeeded( String selectedText ) {
        return selectedText.equals( HumanReadableConstants.INSTANCE.isEqualToNull() ) || selectedText.equals( HumanReadableConstants.INSTANCE.isNotEqualToNull() );
    }

    private HorizontalPanel expressionBuilderLS( final SingleFieldConstraintEBLeftSide con,
                                                 boolean showBinding ) {
        HorizontalPanel ab = new HorizontalPanel();
        ab.setStyleName( "modeller-field-Label" );

        if ( !con.isBound() ) {
            if ( bindable && showBinding && !this.readOnly ) {
                ab.add( new ExpressionBuilder( getModeller(),
                                               getEventBus(),
                                               con.getExpressionLeftSide() ) );
            } else {
                final DRLConstraintValueBuilder constraintValueBuilder = DRLConstraintValueBuilder.getBuilder( getRuleDialect() );
                final ToStringExpressionVisitor visitor = new ToStringExpressionVisitor( constraintValueBuilder );
                ab.add( new SmallLabel( con.getExpressionLeftSide().getText( visitor ) ) );
            }
        } else {
            ab.add( new ExpressionBuilder( getModeller(),
                                           getEventBus(),
                                           con.getExpressionLeftSide() ) );
        }
        return ab;
    }

    /**
     * get the field widget. This may be a simple label, or it may be bound (and
     * show the var name) or a icon to create a binding. It will only show the
     * binding option of showBinding is true.
     */
    private Widget fieldLabel( final SingleFieldConstraint con,
                               final HasConstraints hasConstraints,
                               final boolean showBinding,
                               final int padding ) {
        HorizontalPanel ab = new HorizontalPanel();
        ab.setStyleName( "modeller-field-Label" );

        StringBuilder bindingLabel = new StringBuilder();
        if ( con.isBound() ) {
            bindingLabel.append( "<b>[" );
            bindingLabel.append( con.getFieldBinding() );
            bindingLabel.append( "]</b>&nbsp;" );
        }

        String fieldName = con.getFieldName();
        bindingLabel.append( fieldName );

        if ( bindable && showBinding && !this.readOnly ) {
            ClickHandler click = new ClickHandler() {

                public void onClick( final ClickEvent event ) {
                    //If field name is "this" use parent FactPattern type otherwise we can use the Constraint's field type
                    String fieldName = con.getFieldName();
                    if ( DataType.TYPE_THIS.equals( fieldName ) ) {
                        connectives.getDataModelOracle().getFieldCompletions( pattern.getFactType(),
                                                                              new Callback<ModelField[]>() {
                                                                                  @Override
                                                                                  public void callback( final ModelField[] fields ) {
                                                                                      popupCreator.showBindFieldPopup( pattern,
                                                                                                                       con,
                                                                                                                       fields,
                                                                                                                       popupCreator );
                                                                                  }
                                                                              } );

                    } else {
                        connectives.getDataModelOracle().getFieldCompletions( con.getFieldType(),
                                                                              new Callback<ModelField[]>() {
                                                                                  @Override
                                                                                  public void callback( final ModelField[] fields ) {
                                                                                      popupCreator.showBindFieldPopup( pattern,
                                                                                                                       con,
                                                                                                                       fields,
                                                                                                                       popupCreator );
                                                                                  }
                                                                              } );
                    }
                }
            };
            ClickableLabel cl = new ClickableLabel( bindingLabel.toString(),
                                                    click,
                                                    !this.readOnly );
            DOM.setStyleAttribute( cl.getElement(),
                                   "marginLeft",
                                   "" + padding + "pt" );
            ab.add( cl );
        } else {
            ab.add( new SmallLabel( bindingLabel.toString() ) );
        }

        return ab;
    }

    @Override
    public boolean isReadOnly() {
        return this.readOnly;
    }

    @Override
    public boolean isFactTypeKnown() {
        return this.isFactTypeKnown;
    }

    private void refreshConstraintValueEditorsDropDownData( final SingleFieldConstraint modifiedConstraint ) {
        for ( Map.Entry<SingleFieldConstraint, ConstraintValueEditor> e : constraintValueEditors.entrySet() ) {
            final SingleFieldConstraint sfc = e.getKey();
            if ( sfc.getConstraintValueType() == SingleFieldConstraint.TYPE_LITERAL || sfc.getConstraintValueType() == SingleFieldConstraint.TYPE_ENUM ) {
                if ( !sfc.equals( modifiedConstraint ) ) {
                    e.getValue().refreshEditor();
                }
            }
        }
    }

    private String getRuleDialect() {
        final RuleModel model = getModeller().getModel();
        for ( int i = 0; i < model.attributes.length; i++ ) {
            RuleAttribute attr = model.attributes[ i ];
            if ( attr.getAttributeName().equals( "dialect" ) ) {
                return attr.getValue();
            }
        }
        return DRLConstraintValueBuilder.DEFAULT_DIALECT;
    }
}
